// holds information about tags and how they are to be formatted and treated during cleanup
Refresh.Web.TagDef = function(ids, attributes, isSelfClosing, indentTag, isValidXhtml) {
	
	if (typeof(ids) == 'object') {
		this.Names = ids;
	} else {
		this.Names = [ids];
	}
	
	// semi-universal atttributes
	this.attributes = ['id','class','style','title','dir','lang','accesskey','tabindex'];

	if (attributes) {
		if (attributes[0] == '*') {
			this.attributes = ['*'];
		} else {
			for(var i=0;i<attributes.length;i++) {
				this.attributes.push(attributes[i]);
			}
		}
	}
	
	this.isSelfClosing = (isSelfClosing!=undefined) ? isSelfClosing : false;
	this.indentTag = (indentTag!=undefined) ? indentTag : true;	
	this.isValidXhtml = (isValidXhtml!=undefined) ? isValidXhtml : true;		
};

// used to create all tag defs more quickly
Refresh.Web.CreateTagDefs = function(textTags, blockTags, customTags) {
	var tags = [];


	for(var i=0;i<textTags.length;i++) {
		tags.push(new Refresh.Web.TagDef(textTags[i], [], false, false));
	}	
	for(var i=0;i<blockTags.length;i++) {
		tags.push(new Refresh.Web.TagDef(blockTags[i]));
	}
	for(var i=0;i<customTags.length;i++) {
		tags.push(customTags[i]);
	}	
	
	return tags;
};

Refresh.Web.HtmlCleaner = {
		
	// http://www.w3schools.com/tags/default.asp
	tags: Refresh.Web.CreateTagDefs(
		// text tags that do not get indented
		[
			'abbr',['b','strong'],['i','em'],'u','big','cite','h1','h2','h3','h4','h6','h6','kbd',
			'small','span','strike','sub','sup'
		],
		
		// block elements whose contents are indented
		[
			'p','blockquote','caption','cite','pre','code','dd','dfn','dl','div','dt','fieldset',
			'legend','noframes','noscript','samp','title','tt','var'
		],
		
		// tags with custom attributes and settings
		[
			new Refresh.Web.TagDef('a',['href','target','name'], false, false),
			new Refresh.Web.TagDef('area',['shape','coords','href','target','alt','nohref']),
			new Refresh.Web.TagDef('base',['href','target']),
			new Refresh.Web.TagDef('body',[],false,true),
			new Refresh.Web.TagDef('br',[],true,false),
			new Refresh.Web.TagDef('button',['disabled','type','name','value']),
			new Refresh.Web.TagDef('caption'),
			new Refresh.Web.TagDef('col',['align','char','charoff','span','valign','width']),
			new Refresh.Web.TagDef('colgroup',['align','char','charoff','span','valign','width']),
			new Refresh.Web.TagDef('del',['cite','datetime']),
			new Refresh.Web.TagDef('embed',['*'], true, true, false),			
			new Refresh.Web.TagDef('font',['face','color','size'],false,false,false),			
			new Refresh.Web.TagDef('form',['action','accept','accept-charset','enctype','method','name','target']),
			new Refresh.Web.TagDef('frame',['frameborder','longdesc','marginheight','marginwidth','noresize','scrolling','src']),
			new Refresh.Web.TagDef('frameset',['cols','rows']),
			new Refresh.Web.TagDef('hr',['noshade','size','width'],true,true),
			new Refresh.Web.TagDef('html',['xmlns'],false,true),	
			new Refresh.Web.TagDef('head',[],false,true),
			new Refresh.Web.TagDef('img',['alt','src','border','height','ismap','longdesc','usemap','width'],true),
			new Refresh.Web.TagDef('input',['accept','alt','checked','disabled','maxlenght','name','readonly','size','src','type','value']),
			new Refresh.Web.TagDef('ins',['cite','datetime']),
			new Refresh.Web.TagDef('label',['for']),
			new Refresh.Web.TagDef('li',['type','value']),
			new Refresh.Web.TagDef('link',['charset','href','hreflang','media','rel','rev','target','type'], true),
			new Refresh.Web.TagDef('map',['name']),
			new Refresh.Web.TagDef('meta',['content','http-equiv','name','scheme']),
			new Refresh.Web.TagDef('object',['archive','classid','codebase','codetype','data','declare','height','name','standby','type','usemap','width'], false, true, false),
			new Refresh.Web.TagDef('meta',['content','http-equiv','name','scheme']),
			new Refresh.Web.TagDef('ol',['start','compact','type']),
			new Refresh.Web.TagDef('optgroup',['label','disabled']),
			new Refresh.Web.TagDef('option',['label','disabled','selected','value']),
			new Refresh.Web.TagDef('param',['name','type','value','valuetype'],true,true,true),
			new Refresh.Web.TagDef('q',['type']),
			new Refresh.Web.TagDef('script',['type','charset','defer','src']),
			new Refresh.Web.TagDef('select',['disabled','multiple','name','size']),
			new Refresh.Web.TagDef('style',['type','media']),
			new Refresh.Web.TagDef('table',['border','cellpadding','cellspacing','frame','rules','summary','width']),
			new Refresh.Web.TagDef('tbody',['align','char','charoff','valign']),
			new Refresh.Web.TagDef('td',['abbr','align','axis','char','charoff','colspan','headers','height','nowrap','rowspan','scope','valign','width']),
			new Refresh.Web.TagDef('textarea',['cols','rows','disabled','name','readonly']),
			new Refresh.Web.TagDef('tfoot',['align','char','charoff','valign']),
			new Refresh.Web.TagDef('th',['abbr','align','axis','char','charoff','colspan','headers','height','nowrap','rowspan','scope','valign','width']),
			new Refresh.Web.TagDef('thead',['align','char','charoff','valign']),
			new Refresh.Web.TagDef('title',[],false,false),
			new Refresh.Web.TagDef('tr',['align','char','charoff','valign']),
			new Refresh.Web.TagDef('ul',['compact','type'])
		]),
	
	cleanHtml: function(htmltext) {
		var output = "";		
	},
	
	validateNode: function(node, fullDocument, autoFormat) {
		//return node.innerHTML;
		
		this._output = '';
		this._fullDocument = fullDocument;
		this._autoFormat = autoFormat || true;
		
		this._validateNode(node, 0);
		
		return this._output;
	},
	// recursive inner function
	_validateNode : function(node, level) {
			
		switch (node.nodeType) {
			case 1: // Element (real HTML tag)
				var elementName = node.nodeName.toLowerCase(); 				
				
				var elementAttribs = '';
				if (this._fullDocument) {
					level = (elementName == 'html') ? 0 : level;									
				} else {
					level = (elementName == 'body') ? 0 : level;									
				}
				
				// skip nulls, if not fullDocument mode, skip into body tag
				if (!elementName || (!this._fullDocument && elementName == 'body')) {
					if (node.hasChildNodes()) {
						for (var i=0; i<node.childNodes.length; i++)
							this._validateNode(node.childNodes[i], level);
					}

					return;
				}

				// get tag Data
				var tagData = null;
				for (var i=0; i<this.tags.length; i++) {					
					for (var j=0; j<this.tags[i].Names.length; j++) {					
						if (this.tags[i].Names[j] == elementName) {
							tagData = this.tags[i];
							break;
						}
					}
				}	
				
				// unknown tag
				if (tagData == null) {
					
					if (node.hasChildNodes()) {					
						level++;
						for (var i=0; i<node.childNodes.length; i++)
							this._validateNode(node.childNodes[i],  level);				
						level--;
					}
					return;
					
				} else {
					// use first tag name (b instead of strong)
					elementName = tagData.Names[0];
					
				}					
			
				// Remove empty table tags
				if (elementName == "table" && !node.hasChildNodes()) {
					this._output += "";
					return;
				}

				// add quotes to all attributes
				if (node.attributes.length > 0) {
					for (var i=0; i<node.attributes.length; i++) {	
						if (node.attributes[i].nodeName.indexOf("moz_") > -1)
							continue;		
						if (node.attributes[i].nodeValue == null || node.attributes[i].nodeValue == '')
							continue;	
						if ( (node.attributes[i].nodeName == 'colspan' || node.attributes[i].nodeName == 'rowspan')  && node.attributes[i].nodeValue == '1')
							continue;	

						if ( node.attributes[i].nodeName == 'href') {
							var href = node.getAttribute('temp_href');
							
							if (href == null || href == '')
								href = node.attributes[i].nodeValue;
							elementAttribs += " " + node.attributes[i].nodeName.toLowerCase() + "=" + '"' + this.validateString('' + href) + '"';
							
							continue;
						} else if ( node.attributes[i].nodeName == 'src') {
							var src = node.getAttribute('temp_src');
							//Debugger.writeLine(src);
							if (src == null || src == '')
								src = node.attributes[i].nodeValue;
							elementAttribs += " " + node.attributes[i].nodeName.toLowerCase() + "=" + '"' + this.validateString('' + src) + '"';
							
							continue;
						}		
												
						
						if (tagData.attributes[0] == '*' && node.attributes[i].nodeName.indexOf('temp_') != 0) {
							elementAttribs += " " + node.attributes[i].nodeName.toLowerCase() + "=" + '"' + this.validateString('' + node.attributes[i].nodeValue) + '"';						
						} else {
							// valid tag atts
							for (var j=0; j<tagData.attributes.length; j++) {
								// check this attribute for match with a valid attribute														
								if (tagData.attributes[j] == node.attributes[i].nodeName.toLowerCase()) {
									elementAttribs += " " + node.attributes[i].nodeName.toLowerCase() + "=" + '"' + this.validateString('' + node.attributes[i].nodeValue) + '"';
									break;								
								}
							}
						}
					}
				}
				
				// check for IE's style
				if (Refresh.Web.BrowserInfo.isIE) {
					var style = node.getAttribute('style').cssText.toLowerCase();
					if (style != '') elementAttribs += ' style="' + style + '"'; 
				}
				
				// Add space to elements that need it (empty)
				if ((elementName == "p" || elementName == "td") && (node.innerHTML == "" || node.innerHTML == "&nbsp;")) {
					this._output += "<" + elementName + elementAttribs + ">" + this.validateString(String.fromCharCode(160)) + "</" + elementName + ">";
					return;
				}
				
				// Clean up child nodes
				if (node.hasChildNodes()) {
					var siblingDidNewLine = (this._output.substring(this._output.length-1,this._output.length) == '\n');
											
					// formatting
					if (tagData.indentTag && !siblingDidNewLine) this._output += '\n';
					if (tagData.indentTag) this._output += this.repeat('\t',level);
					
					// write start of outer tag					
					this._output += "<" + elementName + elementAttribs + ">";
				
					// write child tags
					level++;
					for (var i=0; i<node.childNodes.length; i++)
						this._validateNode(node.childNodes[i], level);				
					level--;
					
					// check if the last character of the child contents was a new line
					var childDidNewLine = (this._output.substring(this._output.length-1,this._output.length) == '\n');
										
					// write closing tag
					if (tagData.indentTag && childDidNewLine) 
						this._output += this.repeat('\t',level);
					
					this._output += "</" + elementName + ">";
					
					if (tagData.indentTag) this._output += '\n';					
					
				} else {
					// formatting
					if (tagData.indentTag) this._output += '\n' + this.repeat('\t',level);
				
					if (tagData.isSelfClosing) {
						this._output += "<" + elementName + elementAttribs + " />";
					} else {
						this._output += "<" + elementName + elementAttribs + "></" + elementName + ">";
					}
					
					// formatting
					if (tagData.indentTag || elementName == 'br') 
						this._output += '\n';
				}

				break;
			case 3: // Text
				// Do not convert script elements
				if (node.parentNode.nodeName == "SCRIPT" || node.parentNode.nodeName == "NOSCRIPT" || node.parentNode.nodeName == "STYLE") {
					this._output += node.nodeValue;
					return;
				}

				// validate and strip out whitespace				
				var s = this.validateString(node.nodeValue);
				//var s = node.nodeValue;
				if (s[0] == '\t' || s[0] == '\n') s = s.substr(1,s.length);
				if (s[s.length-1] == '\t' || s[s.length-1] == '\n') s = s.substr(0,s.length-1);				
				s = s.replace(new RegExp('[\\t\\n]','g'), ' ');								
				
				while (s.indexOf('  ') > -1)
					s = s.replace(new RegExp('\\s\\s','g'), ' ');
				
				this._output += s;
				break;
				
			case 8: // Comment
				this._output += "<!--" + node.nodeValue + "-->";
				break;
				
			default: // Unknown
				this._output += "[UNKNOWN NODETYPE " + node.nodeType + "]";
				break;
		}
		
		// clear out extra line breaks and spaces after latest round
		var trailingWhitespace = '';
		for (var i=this._output.length-1; i>=0; i--) {
			var c = this._output.charAt(i);
			
			if ( c == ' ' || c == ' '  || c == '\n' || c == '\t' || c == '\r')
				trailingWhitespace += c;
			else
				break;
		}
		
		if (trailingWhitespace != '') {
			
			// either space or \n based on if there is a \n
			var replaceWith = (trailingWhitespace.indexOf('\n') > -1) ? '\n' : ' ';
			
			this._output = this._output.substring(0,this._output.length-trailingWhitespace.length) + replaceWith;
		
		}

	},
	
	validateString: function(html) {
	    var output = "";

		for (var i=0; i<html.length; i++) {
			var chr = html.charCodeAt(i);

			if (chr > 127 || chr == 60 || chr == 62 || chr == 38 || chr == 39 || chr == 34) {
				output += '&#' + chr + ";";
			} else {
				output += String.fromCharCode(chr);
			}

	    }

	    return output;
	},

	repeat: function(s,c) {
		if (c < 1) return '';
		var r = '';
		for (var i=0; i<c; i++)
			r += s;
		return r;
	}	
};